using System;

namespace ChronoNES
{
    class CPU
    {
        Memory mem;

        ushort PC;
        
        byte S;
        byte A;
        byte X;
        byte Y;
        byte P;

        enum Flags : byte
        {
            Carry       = 0x01,
            Zero        = 0x02,
            IRQDisable  = 0x04,
            Decimal     = 0x08,
            Break       = 0x10,
            Invalid     = 0x20,
            Overflow    = 0x40,
            Negative    = 0x80
        }

        public CPU(Memory _memory)
        {
            mem = _memory;
        }

        public void Init()
        {
            Reset();
        }

        public void Reset()
        {
            PC = mem.Read16(0xFFFC);
            //PC = 0xC000;
            S = 0xFD;
            SetFlag(Flags.IRQDisable, true);
            SetFlag(Flags.Invalid, true);
        }

        public void Step()
        {
            DebugOpcode(mem.Read8(PC));

            ExecuteOpcode(mem.Read8(PC++));
        }

        private void ExecuteOpcode(byte opcode)
        {
            switch (opcode)
            {
                #region Load/Store and transfer

                case 0x8A: Load(ref A, X); break;
                case 0x98: Load(ref A, Y); break;
                case 0xAA: Load(ref X, A); break;
                case 0xBA: Load(ref X, S); break;
                case 0xA8: Load(ref Y, A); break;
                case 0x9A: Load(ref S, X); break;
                           
                case 0xA9: Load(ref A, Immediate()); break;
                case 0xA5: Load(ref A, ZeroPage(0)); break;
                case 0xB5: Load(ref A, ZeroPage(X)); break;
                case 0xAD: Load(ref A, Absolute(0)); break;
                case 0xBD: Load(ref A, Absolute(X)); break;
                case 0xB9: Load(ref A, Absolute(Y)); break;
                case 0xA1: Load(ref A, IndirectX()); break;
                case 0xB1: Load(ref A, IndirectY()); break;
                           
                case 0xA2: Load(ref X, Immediate()); break;
                case 0xA6: Load(ref X, ZeroPage(0)); break;
                case 0xB6: Load(ref X, ZeroPage(Y)); break;
                case 0xAE: Load(ref X, Absolute(0)); break;
                case 0xBE: Load(ref X, Absolute(Y)); break;
                           
                case 0xA0: Load(ref Y, Immediate()); break;
                case 0xA4: Load(ref Y, ZeroPage(0)); break;
                case 0xB4: Load(ref Y, ZeroPage(X)); break;
                case 0xAC: Load(ref Y, Absolute(0)); break;
                case 0xBC: Load(ref Y, Absolute(X)); break;

                case 0x85: Store(ZeroPageA(0), A); break;
                case 0x95: Store(ZeroPageA(X), A); break;
                case 0x8D: Store(AbsoluteA(0), A); break;
                case 0x9D: Store(AbsoluteA(X), A); break;
                case 0x99: Store(AbsoluteA(Y), A); break;
                case 0x81: Store(IndirectXA(), A); break;
                case 0x91: Store(IndirectYA(), A); break;
                
                case 0x86: Store(ZeroPageA(0), X); break;
                case 0x96: Store(ZeroPageA(Y), X); break;
                case 0x8E: Store(AbsoluteA(0), X); break;
                
                case 0x84: Store(ZeroPageA(0), Y); break;
                case 0x94: Store(ZeroPageA(X), Y); break;
                case 0x8C: Store(AbsoluteA(0), Y); break;

                case 0x48: Push(A); break;
                case 0x08: Push(P); break;
                case 0x68: Pull(ref A, true); break;
                case 0x28: Pull(ref P, false); break;

                #endregion

                #region CPU Arithmetic / Logical Operations

                case 0x69: Add(Immediate()); break;
                case 0x65: Add(ZeroPage(0)); break;
                case 0x75: Add(ZeroPage(X)); break;
                case 0x6D: Add(Absolute(0)); break;
                case 0x7D: Add(Absolute(X)); break;
                case 0x79: Add(Absolute(Y)); break;
                case 0x61: Add(IndirectX()); break;
                case 0x71: Add(IndirectY()); break;

                case 0xE9: Sub(Immediate()); break;
                case 0xE5: Sub(ZeroPage(0)); break;
                case 0xF5: Sub(ZeroPage(X)); break;
                case 0xED: Sub(Absolute(0)); break;
                case 0xFD: Sub(Absolute(X)); break;
                case 0xF9: Sub(Absolute(Y)); break;
                case 0xE1: Sub(IndirectX()); break;
                case 0xF1: Sub(IndirectY()); break;

                case 0x29: And(Immediate()); break;
                case 0x25: And(ZeroPage(0)); break;
                case 0x35: And(ZeroPage(X)); break;
                case 0x2D: And(Absolute(0)); break;
                case 0x3D: And(Absolute(X)); break;
                case 0x39: And(Absolute(Y)); break;
                case 0x21: And(IndirectX()); break;
                case 0x31: And(IndirectY()); break;

                case 0x49: Xor(Immediate()); break;
                case 0x45: Xor(ZeroPage(0)); break;
                case 0x55: Xor(ZeroPage(X)); break;
                case 0x4D: Xor(Absolute(0)); break;
                case 0x5D: Xor(Absolute(X)); break;
                case 0x59: Xor(Absolute(Y)); break;
                case 0x41: Xor(IndirectX()); break;
                case 0x51: Xor(IndirectY()); break;

                case 0x09: Or(Immediate()); break;
                case 0x05: Or(ZeroPage(0)); break;
                case 0x15: Or(ZeroPage(X)); break;
                case 0x0D: Or(Absolute(0)); break;
                case 0x1D: Or(Absolute(X)); break;
                case 0x19: Or(Absolute(Y)); break;
                case 0x01: Or(IndirectX()); break;
                case 0x11: Or(IndirectY()); break;

                case 0xC9: Compare(A, Immediate()); break;
                case 0xC5: Compare(A, ZeroPage(0)); break;
                case 0xD5: Compare(A, ZeroPage(X)); break;
                case 0xCD: Compare(A, Absolute(0)); break;
                case 0xDD: Compare(A, Absolute(X)); break;
                case 0xD9: Compare(A, Absolute(Y)); break;
                case 0xC1: Compare(A, IndirectX()); break;
                case 0xD1: Compare(A, IndirectY()); break;

                case 0xE0: Compare(X, Immediate()); break;
                case 0xE4: Compare(X, ZeroPage(0)); break;
                case 0xEC: Compare(X, Absolute(0)); break;

                case 0xC0: Compare(Y, Immediate()); break;
                case 0xC4: Compare(Y, ZeroPage(0)); break;
                case 0xCC: Compare(Y, Absolute(0)); break;

                case 0x24: Test(A, ZeroPage(0)); break;
                case 0x2C: Test(A, Absolute(0)); break;

                case 0xE6: Increment(ZeroPageA(0)); break;
                case 0xF6: Increment(ZeroPageA(X)); break;
                case 0xEE: Increment(AbsoluteA(0)); break;
                case 0xFE: Increment(AbsoluteA(0)); break;
                case 0xE8: Increment(X); break;
                case 0xC8: Increment(Y); break;

                case 0xC6: Decrement(ZeroPageA(0)); break;
                case 0xD6: Decrement(ZeroPageA(X)); break;
                case 0xCE: Decrement(AbsoluteA(0)); break;
                case 0xDE: Decrement(AbsoluteA(0)); break;
                case 0xCA: Decrement(X); break;
                case 0x88: Decrement(Y); break;

                #endregion

                #region CPU Rotate / Shift Instructions
                #endregion

                #region CPU Jump / Control Instructions

                case 0x4C: Jump(Immediate16()); break;
                case 0x6C: Jump(Absolute(0)); break;
                case 0x20: Call(Immediate16()); break;
                case 0x40: ReturnInterrupt(); break;
                case 0x60: Return(); break;

                case 0x10: BranchCondition(Flags.Negative, false); break;
                case 0x30: BranchCondition(Flags.Negative, true); break;
                case 0x50: BranchCondition(Flags.Overflow, false); break;
                case 0x70: BranchCondition(Flags.Overflow, true); break;
                case 0x90: BranchCondition(Flags.Carry, false); break;
                case 0xB0: BranchCondition(Flags.Carry, true); break;
                case 0xD0: BranchCondition(Flags.Zero, false); break;
                case 0xF0: BranchCondition(Flags.Zero, true); break;

                #endregion

                #region CPU Control

                case 0x18: SetFlag(Flags.Carry, false); break;
                case 0x58: SetFlag(Flags.IRQDisable, false); break;
                case 0xD8: SetFlag(Flags.Decimal, false); break;
                case 0xB8: SetFlag(Flags.Overflow, false); break;
                case 0x38: SetFlag(Flags.Carry, true); break;
                case 0x78: SetFlag(Flags.IRQDisable, true); break;
                case 0xF8: SetFlag(Flags.Decimal, true); break;

                case 0xEA: break; // NOP
                
                #endregion

                default:
                    throw new Exception(String.Format("Unknown {0:X} opcode", opcode));
            }
        }
        private byte Immediate()
        {
            return mem.Read8(PC++);
        }
        private ushort Immediate16()
        {
            ushort value = mem.Read16(PC); PC += 2;
            return value;
        }
        private byte ZeroPage(byte _offset)
        {
            return mem.Read8(ZeroPageA(_offset));
        }
        private byte Absolute(byte _offset)
        {
            return mem.Read8(AbsoluteA(_offset));
        }
        private byte IndirectX()
        {
            return mem.Read8(IndirectXA());
        }
        private byte IndirectY()
        {
            return mem.Read8(IndirectYA());
        }

        private ushort ZeroPageA(byte _offset)
        {
            return (ushort)((byte)mem.Read8(PC++) + _offset);
        }
        private ushort AbsoluteA(byte _offset)
        {
            ushort address = (ushort)(mem.Read16(PC) + _offset); 
            PC += 2;
            return address;
        }
        private ushort IndirectXA()
        {
            return mem.Read16((ushort)((byte)mem.Read8(PC++) + X));
        }
        private ushort IndirectYA()
        {
            return (ushort)(mem.Read16(mem.Read8(PC++)) + Y);
        }

        #region Load/Store Operations

        private void Load(ref byte _destination, byte _value)
        {
            _destination = _value;
            SetZero(_destination); SetNegative(_destination);
        }
        private void Store(ushort _address, byte _value)
        {
            mem.write8(_address, _value);
        }

        private void Push(byte _value)
        {
            ushort address = (ushort)(0x1000 | (ushort)S); 
            mem.write8(address, _value);
            S--;
        }
        private void Pull(ref byte _destination, bool _setFlags)
        {
            S++;

            ushort address = (ushort)(0x1000 | (ushort)S);
            _destination = mem.Read8(address);
         
            if (_setFlags)
                SetZero(_destination); SetNegative(_destination);
        }

        #endregion

        #region CPU Arithmetic / Logical Operations

        private void Add(byte _value)
        {
            int fullvalue = (int)(A + _value + (GetFlag(Flags.Carry) ? 1 : 0));
            A = (byte)(fullvalue & 0xFF);
            
            SetNegative(A); 
            SetZero(A);
            SetFlag(Flags.Carry, fullvalue > 0xFF);
            SetFlag(Flags.Overflow, fullvalue > 0xFF);
        }

        private void Sub(byte _value)
        {
            int fullvalue = (int)(A - _value - (1 -(GetFlag(Flags.Carry) ? 1 : 0)));
            A = (byte)(fullvalue & 0xFF);

            SetNegative(A);
            SetZero(A);
            SetFlag(Flags.Carry, !(fullvalue < 0));
            SetFlag(Flags.Overflow, fullvalue < 0);
        }

        private void And(byte _value)
        {
            A &= _value;
            SetNegative(A); SetZero(A);
        }

        private void Xor(byte _value)
        {
            A ^= _value;
            SetNegative(A); SetZero(A);
        }

        private void Or(byte _value)
        {
            A |= _value;
            SetNegative(A); SetZero(A);
        }

        private void Compare(byte _value1, byte _value2)
        {
            byte result = (byte)(_value1 - _value2);
            SetNegative(result);
            SetZero(result);
            SetFlag(Flags.Carry, _value1 >= _value2 ? true: false); // reversed carry
        }

        private void Test(byte _value1, byte _value2)
        {
            SetNegative(_value2); 
            SetZero((byte)(_value1 & _value2));
            SetFlag(Flags.Overflow, (byte)((_value2 >> 6) & 0x1) == 0x1 ? true : false);
        }

        private void Increment(ref byte _value)
        {
            _value++;
            SetNegative(_value); SetZero(_value);
        }
        private void Increment(ushort _address)
        {
            byte value = (byte)(mem.Read8(_address) + 1);
            mem.write8(_address, value);
            SetNegative(value); SetZero(value);
        }

        private void Decrement(ref byte _value)
        {
            _value--;
            SetNegative(_value); SetZero(_value);
        }
        private void Decrement(ushort _address)
        {
            byte value = (byte)(mem.Read8(_address) - 1);
            mem.write8(_address, value);
            SetNegative(value); SetZero(value);
        }

        #endregion

        #region CPU Jump / Control Instructions

        private void Jump(ushort _address)
        {
            PC = _address;
        }

        // TODO - Check
        private void Call(ushort _address)
        {
            S--;
            mem.write16(S, PC);
            S--;
            PC = _address;
        }

        // TODO - Check
        private void ReturnInterrupt()
        {
            Pull(ref P, false);
            PC = mem.Read16(++S); S++;
        }

        // TODO - Check
        private void Return()
        {
            S++;
            PC = mem.Read16(S);
            S++;
        }

        private void BranchCondition(Flags _flag, bool _isSet)
        {
            // could find a better conversion here....
            if ((_isSet && (P & (byte)_flag) != 0) ||
                (!_isSet && (P & (byte)_flag) == 0))
            {
                sbyte offset = (sbyte)mem.Read8(PC++);
                if (offset > 0)
                    PC += (ushort)Math.Abs(offset);
                else
                    PC -= (ushort)Math.Abs(offset);
            }
            else
                PC++;
        }

        #endregion

        #region Processor Status Operations

        private void SetNegative(byte _value)
        {
            SetFlag(Flags.Negative, _value >> 7 == 0x1);
        }
        private void SetZero(byte _value)
        {
            SetFlag(Flags.Zero, _value == 0);
        }
        
        private void SetFlag(Flags _flag, bool _set)
        {
            if (_set)
                P |= (byte)_flag;
            else
                P &= (byte)~_flag;
        }

        private bool GetFlag(Flags _flag)
        {
            if ((byte)(P & (byte)_flag) > 0)
                return true;

            return false;
        }

        #endregion

        private void DebugOpcode(byte _opcode)
        {
            switch (_opcode)
            {
                case 0x8A: DebugOpcodeWithParm(_opcode, "TXA", 0); break;
                case 0x98: DebugOpcodeWithParm(_opcode, "TYA", 0); break;
                case 0xAA: DebugOpcodeWithParm(_opcode, "TAX", 0); break;
                case 0xBA: DebugOpcodeWithParm(_opcode, "TSX", 0); break;
                case 0xA8: DebugOpcodeWithParm(_opcode, "TAY", 0); break;
                case 0x9A: DebugOpcodeWithParm(_opcode, "TXS", 0); break;

                case 0xA9: DebugOpcodeWithParm(_opcode, "LDA", 1); break;
                case 0xA5: DebugOpcodeWithParm(_opcode, "LDA", 1); break;
                case 0xB5: DebugOpcodeWithParm(_opcode, "LDA", 1); break;
                case 0xAD: DebugOpcodeWithParm(_opcode, "LDA", 2); break;
                case 0xBD: DebugOpcodeWithParm(_opcode, "LDA", 2); break;
                case 0xB9: DebugOpcodeWithParm(_opcode, "LDA", 2); break;
                case 0xA1: DebugOpcodeWithParm(_opcode, "LDA", 1); break;
                case 0xB1: DebugOpcodeWithParm(_opcode, "LDA", 1); break;
                                                          
                case 0xA2: DebugOpcodeWithParm(_opcode, "LDX", 1); break;
                case 0xA6: DebugOpcodeWithParm(_opcode, "LDX", 1); break;
                case 0xB6: DebugOpcodeWithParm(_opcode, "LDX", 1); break;
                case 0xAE: DebugOpcodeWithParm(_opcode, "LDX", 2); break;
                case 0xBE: DebugOpcodeWithParm(_opcode, "LDX", 2); break;

                case 0xA0: DebugOpcodeWithParm(_opcode, "LDY", 1); break;
                case 0xA4: DebugOpcodeWithParm(_opcode, "LDY", 1); break;
                case 0xB4: DebugOpcodeWithParm(_opcode, "LDY", 1); break;
                case 0xAC: DebugOpcodeWithParm(_opcode, "LDY", 2); break; 
                case 0xBC: DebugOpcodeWithParm(_opcode, "LDY", 2); break;
                
                case 0x85: DebugOpcodeWithParm(_opcode, "STA", 1); break;
                case 0x95: DebugOpcodeWithParm(_opcode, "STA", 1); break;
                case 0x8D: DebugOpcodeWithParm(_opcode, "STA", 2); break;
                case 0x9D: DebugOpcodeWithParm(_opcode, "STA", 2); break;
                case 0x99: DebugOpcodeWithParm(_opcode, "STA", 2); break;
                case 0x81: DebugOpcodeWithParm(_opcode, "STA", 1); break;
                case 0x91: DebugOpcodeWithParm(_opcode, "STA", 1); break;
                
                case 0x86: DebugOpcodeWithParm(_opcode, "STX", 1); break;
                case 0x96: DebugOpcodeWithParm(_opcode, "STX", 1); break;
                case 0x8E: DebugOpcodeWithParm(_opcode, "STX", 2); break;
                
                case 0x84: DebugOpcodeWithParm(_opcode, "STY", 1); break;
                case 0x94: DebugOpcodeWithParm(_opcode, "STY", 1); break;
                case 0x8C: DebugOpcodeWithParm(_opcode, "STY", 2); break;
                
                case 0x48: DebugOpcodeWithParm(_opcode, "PHA", 0); break;
                case 0x08: DebugOpcodeWithParm(_opcode, "PHP", 0); break;
                case 0x68: DebugOpcodeWithParm(_opcode, "PLA", 0); break;
                case 0x28: DebugOpcodeWithParm(_opcode, "PLP", 0); break;

                case 0x69: DebugOpcodeWithParm(_opcode, "ADC", 1); break;
                case 0x65: DebugOpcodeWithParm(_opcode, "ADC", 1); break;
                case 0x75: DebugOpcodeWithParm(_opcode, "ADC", 1); break;
                case 0x6D: DebugOpcodeWithParm(_opcode, "ADC", 2); break;
                case 0x7D: DebugOpcodeWithParm(_opcode, "ADC", 2); break;
                case 0x79: DebugOpcodeWithParm(_opcode, "ADC", 2); break;
                case 0x61: DebugOpcodeWithParm(_opcode, "ADC", 1); break;
                case 0x71: DebugOpcodeWithParm(_opcode, "ADC", 1); break;
                
                case 0xE9: DebugOpcodeWithParm(_opcode, "SBC", 1); break;
                case 0xE5: DebugOpcodeWithParm(_opcode, "SBC", 1); break;
                case 0xF5: DebugOpcodeWithParm(_opcode, "SBC", 1); break;
                case 0xED: DebugOpcodeWithParm(_opcode, "SBC", 2); break;
                case 0xFD: DebugOpcodeWithParm(_opcode, "SBC", 2); break;
                case 0xF9: DebugOpcodeWithParm(_opcode, "SBC", 2); break;
                case 0xE1: DebugOpcodeWithParm(_opcode, "SBC", 1); break;
                case 0xF1: DebugOpcodeWithParm(_opcode, "SBC", 1); break;

                case 0x29: DebugOpcodeWithParm(_opcode, "AND", 1); break;
                case 0x25: DebugOpcodeWithParm(_opcode, "AND", 1); break;
                case 0x35: DebugOpcodeWithParm(_opcode, "AND", 1); break;
                case 0x2D: DebugOpcodeWithParm(_opcode, "AND", 2); break;
                case 0x3D: DebugOpcodeWithParm(_opcode, "AND", 2); break;
                case 0x39: DebugOpcodeWithParm(_opcode, "AND", 2); break;
                case 0x21: DebugOpcodeWithParm(_opcode, "AND", 1); break;
                case 0x31: DebugOpcodeWithParm(_opcode, "AND", 1); break;
                
                case 0x49: DebugOpcodeWithParm(_opcode, "EOR", 1); break;
                case 0x45: DebugOpcodeWithParm(_opcode, "EOR", 1); break;
                case 0x55: DebugOpcodeWithParm(_opcode, "EOR", 1); break;
                case 0x4D: DebugOpcodeWithParm(_opcode, "EOR", 2); break;
                case 0x5D: DebugOpcodeWithParm(_opcode, "EOR", 2); break;
                case 0x59: DebugOpcodeWithParm(_opcode, "EOR", 2); break;
                case 0x41: DebugOpcodeWithParm(_opcode, "EOR", 1); break;
                case 0x51: DebugOpcodeWithParm(_opcode, "EOR", 1); break;
                
                case 0x09: DebugOpcodeWithParm(_opcode, "ORA", 1); break;
                case 0x05: DebugOpcodeWithParm(_opcode, "ORA", 1); break;
                case 0x15: DebugOpcodeWithParm(_opcode, "ORA", 1); break;
                case 0x0D: DebugOpcodeWithParm(_opcode, "ORA", 2); break;
                case 0x1D: DebugOpcodeWithParm(_opcode, "ORA", 2); break;
                case 0x19: DebugOpcodeWithParm(_opcode, "ORA", 2); break;
                case 0x01: DebugOpcodeWithParm(_opcode, "ORA", 1); break;
                case 0x11: DebugOpcodeWithParm(_opcode, "ORA", 1); break;
                
                case 0xC9: DebugOpcodeWithParm(_opcode, "CMP", 1); break;
                case 0xC5: DebugOpcodeWithParm(_opcode, "CMP", 1); break;
                case 0xD5: DebugOpcodeWithParm(_opcode, "CMP", 1); break;
                case 0xCD: DebugOpcodeWithParm(_opcode, "CMP", 2); break;
                case 0xDD: DebugOpcodeWithParm(_opcode, "CMP", 2); break;
                case 0xD9: DebugOpcodeWithParm(_opcode, "CMP", 2); break;
                case 0xC1: DebugOpcodeWithParm(_opcode, "CMP", 1); break;
                case 0xD1: DebugOpcodeWithParm(_opcode, "CMP", 1); break;
                case 0xE0: DebugOpcodeWithParm(_opcode, "CPX", 1); break;
                case 0xE4: DebugOpcodeWithParm(_opcode, "CPX", 1); break;
                case 0xEC: DebugOpcodeWithParm(_opcode, "CPX", 2); break;
                case 0xC0: DebugOpcodeWithParm(_opcode, "CPY", 1); break;
                case 0xC4: DebugOpcodeWithParm(_opcode, "CPY", 1); break;
                case 0xCC: DebugOpcodeWithParm(_opcode, "CPY", 2); break;

                case 0x24: DebugOpcodeWithParm(_opcode, "BIT", 1); break;
                case 0x2C: DebugOpcodeWithParm(_opcode, "BIT", 2); break;

                case 0xE6: DebugOpcodeWithParm(_opcode, "INC", 1); break;
                case 0xF6: DebugOpcodeWithParm(_opcode, "INC", 1); break;
                case 0xEE: DebugOpcodeWithParm(_opcode, "INC", 2); break;
                case 0xFE: DebugOpcodeWithParm(_opcode, "INC", 2); break;
                case 0xE8: DebugOpcodeWithParm(_opcode, "INX", 0); break;
                case 0xC8: DebugOpcodeWithParm(_opcode, "INY", 0); break;

                case 0xC6: DebugOpcodeWithParm(_opcode, "DEC", 1); break;
                case 0xD6: DebugOpcodeWithParm(_opcode, "DEC", 1); break;
                case 0xCE: DebugOpcodeWithParm(_opcode, "DEC", 2); break;
                case 0xDE: DebugOpcodeWithParm(_opcode, "DEC", 2); break;
                case 0xCA: DebugOpcodeWithParm(_opcode, "DEX", 0); break;
                case 0x88: DebugOpcodeWithParm(_opcode, "DEY", 0); break;

                case 0x4C: DebugOpcodeWithParm(_opcode, "JMP", 2); break;
                case 0x6C: DebugOpcodeWithParm(_opcode, "JMP", 2); break;
                case 0x20: DebugOpcodeWithParm(_opcode, "JSR", 2); break;
                case 0x40: DebugOpcodeWithParm(_opcode, "RTI", 0); break;
                case 0x60: DebugOpcodeWithParm(_opcode, "RTS", 0); break;

                case 0x10: DebugOpcodeWithParm(_opcode, "BPL", 1); break;
                case 0x30: DebugOpcodeWithParm(_opcode, "BMI", 1); break;
                case 0x50: DebugOpcodeWithParm(_opcode, "BVC", 1); break;
                case 0x70: DebugOpcodeWithParm(_opcode, "BVS", 1); break;
                case 0x90: DebugOpcodeWithParm(_opcode, "BCC", 1); break;
                case 0xB0: DebugOpcodeWithParm(_opcode, "BCS", 1); break;
                case 0xD0: DebugOpcodeWithParm(_opcode, "BNE", 1); break;
                case 0xF0: DebugOpcodeWithParm(_opcode, "BEQ", 1); break;

                case 0x18: DebugOpcodeWithParm(_opcode, "CLC", 0); break;
                case 0x58: DebugOpcodeWithParm(_opcode, "CLI", 0); break;
                case 0xD8: DebugOpcodeWithParm(_opcode, "CLD", 0); break;
                case 0xB8: DebugOpcodeWithParm(_opcode, "CLV", 0); break;
                case 0x38: DebugOpcodeWithParm(_opcode, "SEC", 0); break;
                case 0x78: DebugOpcodeWithParm(_opcode, "SEI", 0); break;
                case 0xF8: DebugOpcodeWithParm(_opcode, "SED", 0); break;

                case 0xEA: DebugOpcodeWithParm(_opcode, "NOP", 0); break;
            }
        }

        private void DebugOpcodeWithParm(byte _opcode, String _name, int _parms)
        {
            String op_name = _name;
            String op_parm1 = "";
            String op_parm2 = "";

            if (_parms >= 1)
                op_parm1 = String.Format("{0,2:X2}", mem.Read8((ushort)(PC + 1)));
            if (_parms >= 2)
                op_parm2 = String.Format("{0,2:X2}", mem.Read8((ushort)(PC + 2)));

            Log.Info(String.Format("{0,4:X4}  {1,2:X2} {2,2} {3,2}  {4}       A:{5,2:X2} X:{6,2:X2} Y:{7,2:X2} P:{8,2:X2} SP:{9,2:X2}    {10,2:X2} {11,2:X2} {12,2:X2} {13,2:X2} {14,2:X2} {15,2:X2}",
                                    PC, _opcode, op_parm1, op_parm2, op_name, A, X, Y, P, S, mem.Read8(0x10F9), mem.Read8(0x10FA), mem.Read8(0x10FB), mem.Read8(0x10FC), mem.Read8(0x10FD), mem.Read8(0x10FF)));
        }
    }
}